/**
 * 20.1.9：更改copy为带可选项recursive的Object3D拷贝方法，而非类Sprite的
 * 20.7.30：更改clone为以结构体实例化，继承类若无属性变更就可以不用重写clone了
 * 200909:移除射线相关代码，与three.js保持一致
 * 201215：增加圆角边框，移除属性中的材质通用属性
 * 210714：升级为支持多行,新增相关属性
 * 210722:增加滤除空格属性，居中选项
 * 20220304:[require] to [import]...jordia
 * 20220721:add annotation for constructor
 * 20220802:add [fontWeight]
 */
import Utils from "../general/utils";
import {Color,Sprite,SpriteMaterial,Object3D,Texture,LinearFilter} from 'three';
import Props from '../general/props';

const PI=Math.PI;
const HPI=Math.PI*0.5;

/**
 文字或贴图sprite
 材质通用属性可获取material进行编辑
 */
export default class Text extends Sprite{
    /**
     * 
     * @param {*} values 可选参数对象
     *  {
     *    pixelRatio:1.5, 像素比（适配不同平台字体大小）
     *    map:null,  贴图
     *    fontSize:16, 字体大小
     *    text:'', 文字内容
     *    color:new Color(0,0,0), 文字颜色
     *    fontFamily:'Arial', 字体
     *    fontWeight:'normal', 字体样式
     *    fontStyle:'normal', 字体样式
     *    vivid:1, 清晰度
     *    background:null, 背景颜色
     *    border:null, 边框颜色
     *    borderRadiusLeftBottom:0, 边框圆角
     *    borderRadiusLeftTop:0, 边框圆角
     *    borderRadiusRightTop:0, 边框圆角
     *    borderRadiusRightBottom:0, 边框圆角
     *    lineWidth:0.04, 边框厚度（小于1按比例，大于1按像素）
     *    rowLength:20, 每行字数
     *    marginHorizontal:20, 水平边距（像素）
     *    marginVertical:15, 垂直边距（像素）
     *    offsetRatioVertical:0.35, 垂直偏移（字体大小比例）
     *    lineHeightRatio:1.1, 行高（字体大小比例）
     *    filterSpace:false，滤除空格（滤除首位所有空格，滤除连续的多个空格为一个）
     *    alignCenter:false，是否居中（多行时）
     *   } 
     */
    constructor(values={}) {
        super();
        this._values=values;
        this.material=new SpriteMaterial();
        this._init();
    }

    //----------------------------------------对外属性及方法----------------------------------------

    /**
     * 获取属性
     * @returns {props}
     */
    get props(){
        return this._props;
    }

    /**
     * 设置属性
     * @param values
     */
    set props(values){
        for(let key in values){
            if(this._props[''+key+'']){
                this._props[''+key+''].value=values[''+key+''];
            }else{
                console.warn(''+key+' is not a prop of text!');
            }
        }
    }

    get pixelRatio(){
        return this.props.pixelRatio.value;
    }
    set pixelRatio(num){
        this.props.pixelRatio.value=num;
    }

    get fontSize(){
        return this.props.fontSize.value;
    }
    set fontSize(num){
        this.props.fontSize.value=num;
    }

    get text(){
        return this.props.text.value;
    }
    set text(text){
        this.props.text.value=text;
    }

    get color(){
        return this.props.color.value;
    }
    set color(color){
        this.props.color.value=color;
    }

    get fontFamily(){
        return this.props.fontFamily.value;
    }
    set fontFamily(fontFamily){
        this.props.fontFamily.value=fontFamily;
    }

    get fontStyle(){
        return this.props.fontStyle.value;
    }
    set fontStyle(fontStyle){
        this.props.fontStyle.value=fontStyle;
    }

    get fontWeight(){
        return this.props.fontWeight.value;
    }
    set fontWeight(fontWeight){
        this.props.fontWeight.value=fontWeight;
    }

    get vivid(){
        return this.props.vivid.value;
    }
    set vivid(num){
        this.props.vivid.value=num;
    }

    get background(){
        return this.props.background.value;
    }
    set background(color){
        this.props.background.value=color;
    }

    get border(){
        return this.props.border.value;
    }
    set border(color){
        this.props.border.value=color;
    }

    get borderRadiusLeftBottom(){
        return this.props.borderRadiusLeftBottom.value;
    }
    set borderRadiusLeftBottom(num){
        this.props.borderRadiusLeftBottom.value=num;
    }

    get borderRadiusLeftTop(){
        return this.props.borderRadiusLeftTop.value;
    }
    set borderRadiusLeftTop(num){
        this.props.borderRadiusLeftTop.value=num;
    }

    get borderRadiusRightTop(){
        return this.props.borderRadiusRightTop.value;
    }
    set borderRadiusRightTop(num){
        this.props.borderRadiusRightTop.value=num;
    }

    get borderRadiusRightBottom(){
        return this.props.borderRadiusRightBottom.value;
    }
    set borderRadiusRightBottom(num){
        this.props.borderRadiusRightBottom.value=num;
    }

    get lineWidth(){
        return this.props.lineWidth.value;
    }
    set lineWidth(num){
        this.props.lineWidth.value=num;
    }

    get rowLength(){
        return this.props.rowLength.value;
    }
    set rowLength(num){
        this.props.rowLength.value=num;
    }

    get marginHorizontal(){
        return this.props.marginHorizontal.value;
    }
    set marginHorizontal(num){
        this.props.marginHorizontal.value=num;
    }

    get marginVertical(){
        return this.props.marginVertical.value;
    }
    set marginVertical(num){
        this.props.marginVertical.value=num;
    }

    get offsetRatioVertical(){
        return this.props.offsetRatioVertical.value;
    }
    set offsetRatioVertical(num){
        this.props.offsetRatioVertical.value=num;
    }

    get lineHeightRatio(){
        return this.props.lineHeightRatio.value;
    }
    set lineHeightRatio(num){
        this.props.lineHeightRatio.value=num;
    }

    get filterSpace(){
        return this.props.filterSpace.value;
    }
    set filterSpace(num){
        this.props.filterSpace.value=num;
    }

    get alignCenter(){
        return this.props.alignCenter.value;
    }
    set alignCenter(bol){
        this.props.alignCenter.value=bol;
    }

    copy(source,recursive=false){
        Object3D.prototype.copy.call( this, source,recursive);
        this.props=source['props'].all(true);
        this.material.copy(source['material']);
        return this;
    }

    /**
     * 克隆
     * @param recursive {boolean}
     * @returns {Text}
     */
    clone(recursive=false){
        // return new Text().copy(this,recursive);..............20.7.30
        return new this.constructor().copy(this,recursive);
    }

    /**
     * 销毁
     */
    dispose(){
        this.material.map.dispose();
        this.material.dispose();
        this._props.dispose();
        this._props=null;
    }

    //-----------------------------------------私有属性及方法---------------------------------------------

    _init(){
        this._initProps();
        this._initVariable();
        this._initMaterial();
        this._initPropCallback();
    }
    _initProps(){
        this._props=new Props({
            pixelRatio:this._values.pixelRatio||1.5,
            map:this._values.map||null,
            fontSize:this._values.fontSize||16,
            text:this._values.text||'',
            color:this._values.color||new Color(0,0,0),
            fontFamily:this._values.fontFamily||'Arial',
            fontStyle:this._values.fontStyle||'normal',
            fontWeight:this._values.fontWeight||'normal',
            vivid:this._values.vivid||1,
            background:this._values.background?this._values.background:null,
            border:this._values.border?this._values.border:null,
            borderRadiusLeftBottom:this._values.borderRadiusLeftBottom?this._values.borderRadiusLeftBottom:0,
            borderRadiusLeftTop:this._values.borderRadiusLeftTop?this._values.borderRadiusLeftTop:0,
            borderRadiusRightTop:this._values.borderRadiusRightTop?this._values.borderRadiusRightTop:0,
            borderRadiusRightBottom:this._values.borderRadiusRightBottom?this._values.borderRadiusRightBottom:0,
            lineWidth:this._values.lineWidth||0.04,
            rowLength:this._values.rowLength||20,
            marginHorizontal:this._values.marginHorizontal||20,
            marginVertical:this._values.marginVertical||15,
            offsetRatioVertical:this._values.offsetRatioVertical||0.35,
            lineHeightRatio:this._values.lineHeightRatio||1.1,
            filterSpace:this._values.filterSpace||false,
            alignCenter:this._values.alignCenter||false,
        });
    }
    _initPropCallback(){
        this._props.onKeyChange('map',()=>{
            this.material.map=this._props.map.value;
            this.material.needsUpdate=true;
        });
        this._props.onMultiKeyChange(()=>{
            this.material.map.dispose();
            this._drawText();
        },'fontSize','text','color','fontFamily','fontStyle','fontWeight','vivid','background','border','borderRadiusLeftBottom',
            'borderRadiusLeftTop','borderRadiusRightTop','borderRadiusRightBottom','lineWidth','rowLength','marginHorizontal',
            'marginVertical','offsetRatioVertical','lineHeightRatio','filterSpace','alignCenter');
    }
    _initVariable(){
        this._tA0=[];
        this._tA1=[];
        this._tA2=[];
        this._tA3=[];
    }
    _checkEndSpace(ary){
        if(ary.length>0&&ary[0]===' ')ary.splice(0,1);
        if(ary.length>0&&ary[ary.length-1]===' ')ary.splice(ary.length-1,1);
    }
    _splitRow(_text,out){
        let text=_text;
        if(this._props.filterSpace.value)text=Utils.filterSpace(_text);
        out.length=0;
        let ws=this._tA0;
        ws.length=0;
        ws[0]=0;
        let rw=0;
        let rl=this._props.rowLength.value;

        this._tA3.length=0;

        for(let a = 0; a < text.length; a++){
            rw=text.charCodeAt(a)>255?1:0.5;
            if(rw+ws[ws.length-1] >rl ){

                if(this._props.filterSpace.value)this._checkEndSpace(this._tA3);
                out.push(this._tA3.join(''));

                ws.push(0);
                this._tA3.length =0;
            }
            this._tA3.push(text[a]);
            ws[ws.length-1]+=rw;
        }
        if(this._props.filterSpace.value)this._checkEndSpace(this._tA3);
        out.push(this._tA3.join(''));
    }
    _drawText(){
        let row=this._tA1;
        let text = this._props.text.value+'';
        this._splitRow(text,row);
        let mh=this._props.marginHorizontal.value;
        let mv=this._props.marginVertical.value;
        let ov=this._props.offsetRatioVertical.value;
        let lineRatio=this._props.lineHeightRatio.value;
        let _fontSize = this._props.fontSize.value * this._props.pixelRatio.value * this._props.vivid.value;
        let ratBasic = 100 * 16 / this._props.fontSize.value * this._props.vivid.value;

        let canvas = document.createElement('canvas');
        let ctx = canvas.getContext('2d');
        ctx.font = ''+this._props.fontWeight.value+' ' + this._props.fontStyle.value + ' ' + _fontSize + 'px ' + this._props.fontFamily.value + '';
        let maxW=Number.NEGATIVE_INFINITY;
        this._tA2.length=0;
        for(let ro of row){
            let cw=ctx.measureText(ro).width+mh*2;
            this._tA2.push(cw);
            if(maxW<cw){
                maxW=cw;
            }
        }
        canvas.width=maxW;
        canvas.height = _fontSize * row.length*lineRatio+mv*2;
        let ratX = canvas.width / ratBasic;
        let ratY = canvas.height / canvas.width * ratX;
        let lineWidth=this._props.lineWidth.value<1?canvas.height * this._props.lineWidth.value:this._props.lineWidth.value;

        if (this._props.background.value&&!this._props.border.value) {
            ctx.rect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#' + this._props.background.value.getHexString();
            ctx.fill();
        }
        if (this._props.border.value) {
            ctx.lineWidth = lineWidth;
            let w = lineWidth;

            let width=canvas.width,height=canvas.height;
            let r0=this._props.borderRadiusLeftBottom.value;
            let r1=this._props.borderRadiusLeftTop.value;
            let r2=this._props.borderRadiusRightTop.value;
            let r3=this._props.borderRadiusRightBottom.value;
            ctx.arc(w+r0,height-w-r0,r0,HPI,PI,false);
            ctx.arc(w+r1,w+r1,r1,PI,-HPI,false);
            ctx.arc(width-w-r2,w+r2,r2,-HPI,0,false);
            ctx.arc(width-w-r3,height-w-r3,r3,0,HPI,false);
            ctx.lineTo(w+r0,height-w);
            ctx.fillStyle = '#' + this._props.background.value.getHexString();
            ctx.fill();
            ctx.strokeStyle = '#' + this._props.border.value.getHexString();
            ctx.stroke();
        }
        ctx.font = ''+this._props.fontWeight.value+' ' + this._props.fontStyle.value + ' ' + _fontSize + 'px ' + this._props.fontFamily.value + '';
        ctx.fillStyle = '#' + this._props.color.value.getHexString();

        for(let b = 0; b < row.length; b++) {
            let sx=this._props.alignCenter.value?mh+(maxW-this._tA2[b])*0.5:mh;
            ctx.fillText(row[b], sx, (b + 0.5) * _fontSize * lineRatio+_fontSize*ov+mv);
        }

        this.material.map = new Texture(canvas);
        this.material.map.minFilter = LinearFilter;
        this.material.map.magFilter = LinearFilter;
        this.material.map.needsUpdate = true;
        this.scale.set(ratX, ratY, 1);
    }
    _initMaterial() {
        if(!this._props.map.value) {
            this._drawText();
        }else{
            this.material.map=this._props.map.value;
            this.material.color=this._props.color.value;
        }
        this.material.transparent = true;
        this.material.alphaTest = 0.1;
    }
}